using System;
using System.IO;
using System.Text;
using System.Threading;

using PickGold.Exceptions;

namespace PickGold.Zip
{
	public partial class ZipEntry
	{
		internal void WriteCentralDirectoryEntry(Stream s)
		{
			byte[] bytes = new byte[4096];
			int i = 0;
			// signature
			bytes[i++] = (byte)(ZipConstants.ZipDirEntrySignature & 0x000000FF);
			bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0x0000FF00) >> 8);
			bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0x00FF0000) >> 16);
			bytes[i++] = (byte)((ZipConstants.ZipDirEntrySignature & 0xFF000000) >> 24);

			// Version Made By
			// workitem 7071
			// We must not overwrite the VersionMadeBy field when writing out a zip
			// archive.  The VersionMadeBy tells the zip reader the meaning of the
			// File attributes.  Overwriting the VersionMadeBy will result in
			// inconsistent metadata.  Consider the scenario where the application
			// opens and reads a zip file that had been created on Linux. Then the
			// app adds one file to the Zip archive, and saves it.  The file
			// attributes for all the entries added on Linux will be significant for
			// Linux.  Therefore the VersionMadeBy for those entries must not be
			// changed.  Only the entries that are actually created on Windows NTFS
			// should get the VersionMadeBy indicating Windows/NTFS.
			bytes[i++] = (byte)(_VersionMadeBy & 0x00FF);
			bytes[i++] = (byte)((_VersionMadeBy & 0xFF00) >> 8);

			// Apparently we want to duplicate the extra field here; we cannot
			// simply zero it out and assume tools and apps will use the right one.

			////Int16 extraFieldLengthSave = (short)(_EntryHeader[28] + _EntryHeader[29] * 256);
			////_EntryHeader[28] = 0;
			////_EntryHeader[29] = 0;

			// Version Needed, Bitfield, compression method, lastmod,
			// crc, compressed and uncompressed sizes, filename length and extra field length.
			// These are all present in the local file header, but they may be zero values there.
			// So we cannot just copy them.

			// workitem 11969: Version Needed To Extract in central directory must be
			// the same as the local entry or MS .NET System.IO.Zip fails read.
			var vNeeded = (short)(VersionNeeded != 0 ? VersionNeeded : 20);
			// workitem 12964
			if (this._OutputUsesZip64 == null)
			{
				// a zipentry in a zipoutputstream, with zero bytes written
				this._OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always);
			}

			var versionNeededToExtract = (short)(_OutputUsesZip64.Value ? 45 : vNeeded);
#if BZIP
			if (this.CompressionMethod == PickGold.Zip.CompressionMethod.BZip2)
				versionNeededToExtract = 46;
#endif

			bytes[i++] = (byte)(versionNeededToExtract & 0x00FF);
			bytes[i++] = (byte)((versionNeededToExtract & 0xFF00) >> 8);

			bytes[i++] = (byte)(_BitField & 0x00FF);
			bytes[i++] = (byte)((_BitField & 0xFF00) >> 8);

			bytes[i++] = (byte)(_CompressionMethod & 0x00FF);
			bytes[i++] = (byte)((_CompressionMethod & 0xFF00) >> 8);

#if AESCRYPTO
			if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
			Encryption == EncryptionAlgorithm.WinZipAes256)
			{
				i -= 2;
				bytes[i++] = 0x63;
				bytes[i++] = 0;
			}
#endif

			bytes[i++] = (byte)(_TimeBlob & 0x000000FF);
			bytes[i++] = (byte)((_TimeBlob & 0x0000FF00) >> 8);
			bytes[i++] = (byte)((_TimeBlob & 0x00FF0000) >> 16);
			bytes[i++] = (byte)((_TimeBlob & 0xFF000000) >> 24);
			bytes[i++] = (byte)(_Crc32 & 0x000000FF);
			bytes[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
			bytes[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
			bytes[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);

			var j = 0;
			if (this._OutputUsesZip64.Value)
			{
				// CompressedSize (Int32) and UncompressedSize - all 0xFF
				for (j = 0; j < 8; j++)
					bytes[i++] = 0xFF;
			}
			else
			{
				bytes[i++] = (byte)(_CompressedSize & 0x000000FF);
				bytes[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
				bytes[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
				bytes[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);

				bytes[i++] = (byte)(_UncompressedSize & 0x000000FF);
				bytes[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
				bytes[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
				bytes[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
			}

			var fileNameBytes = GetEncodedFileNameBytes();
			var filenameLength = (short)fileNameBytes.Length;
			bytes[i++] = (byte)(filenameLength & 0x00FF);
			bytes[i++] = (byte)((filenameLength & 0xFF00) >> 8);

			// do this again because now we have real data
			this._presumeZip64 = _OutputUsesZip64.Value;

			// workitem 11131
			//
			// cannot generate the extra field again, here's why: In the case of a
			// zero-byte entry, which uses encryption, DotNetZip will "remove" the
			// encryption from the entry.  It does this in PostProcessOutput; it
			// modifies the entry header, and rewrites it, resetting the Bitfield
			// (one bit indicates encryption), and potentially resetting the
			// compression method - for AES the Compression method is 0x63, and it
			// would get reset to zero (no compression).  It then calls SetLength()
			// to truncate the stream to remove the encryption header (12 bytes for
			// AES256).  But, it leaves the previously-generated "Extra Field"
			// metadata (11 bytes) for AES in the entry header. This extra field
			// data is now "orphaned" - it refers to AES encryption when in fact no
			// AES encryption is used. But no problem, the PKWARE spec says that
			// unrecognized extra fields can just be ignored. ok.  After "removal"
			// of AES encryption, the length of the Extra Field can remains the
			// same; it's just that there will be 11 bytes in there that previously
			// pertained to AES which are now unused. Even the field code is still
			// there, but it will be unused by readers, as the encryption bit is not
			// set.
			//
			// Re-calculating the Extra field now would produce a block that is 11
			// bytes shorter, and that mismatch - between the extra field in the
			// local header and the extra field in the Central Directory - would
			// cause problems. (where? why? what problems?)  So we can't do
			// that. It's all good though, because though the content may have
			// changed, the length definitely has not. Also, the _EntryHeader
			// contains the "updated" extra field (after PostProcessOutput) at
			// offset (30 + filenameLength).

			this._Extra = ConstructExtraField(true);

			var extraFieldLength = (short)((this._Extra == null) ? 0 : this._Extra.Length);
			bytes[i++] = (byte)(extraFieldLength & 0x00FF);
			bytes[i++] = (byte)((extraFieldLength & 0xFF00) >> 8);

			// File (entry) Comment Length
			// the _CommentBytes private field was set during WriteHeader()
			var commentLength = (this._CommentBytes == null) ? 0 : this._CommentBytes.Length;

			// the size of our buffer defines the max length of the comment we can write
			if (commentLength + i > bytes.Length)
				commentLength = bytes.Length - i;
			bytes[i++] = (byte)(commentLength & 0x00FF);
			bytes[i++] = (byte)((commentLength & 0xFF00) >> 8);

			// Disk number start
			var segmented = (this._container.ZipFile != null) && (this._container.ZipFile.MaxOutputSegmentSize != 0);
			if (segmented) // workitem 13915
			{
				// Emit nonzero disknumber only if saving segmented archive.
				bytes[i++] = (byte)(_diskNumber & 0x00FF);
				bytes[i++] = (byte)((_diskNumber & 0xFF00) >> 8);
			}
			else
			{
				// If reading a segmneted archive and saving to a regular archive,
				// ZipEntry._diskNumber will be non-zero but it should be saved as
				// zero.
				bytes[i++] = 0;
				bytes[i++] = 0;
			}

			// internal file attrs
			// workitem 7801
			bytes[i++] = (byte)((_IsText) ? 1 : 0); // lo bit: filetype hint.  0=bin, 1=txt.
			bytes[i++] = 0;

			// external file attrs
			// workitem 7071
			bytes[i++] = (byte)(_ExternalFileAttrs & 0x000000FF);
			bytes[i++] = (byte)((_ExternalFileAttrs & 0x0000FF00) >> 8);
			bytes[i++] = (byte)((_ExternalFileAttrs & 0x00FF0000) >> 16);
			bytes[i++] = (byte)((_ExternalFileAttrs & 0xFF000000) >> 24);

			// workitem 11131
			// relative offset of local header.
			//
			// If necessary to go to 64-bit value, then emit 0xFFFFFFFF,
			// else write out the value.
			//
			// Even if zip64 is required for other reasons - number of the entry
			// > 65534, or uncompressed size of the entry > MAX_INT32, the ROLH
			// need not be stored in a 64-bit field .
			if (this._RelativeOffsetOfLocalHeader > 0xFFFFFFFFL) // _OutputUsesZip64.Value
			{
				bytes[i++] = 0xFF;
				bytes[i++] = 0xFF;
				bytes[i++] = 0xFF;
				bytes[i++] = 0xFF;
			}
			else
			{
				bytes[i++] = (byte)(this._RelativeOffsetOfLocalHeader & 0x000000FF);
				bytes[i++] = (byte)((this._RelativeOffsetOfLocalHeader & 0x0000FF00) >> 8);
				bytes[i++] = (byte)((this._RelativeOffsetOfLocalHeader & 0x00FF0000) >> 16);
				bytes[i++] = (byte)((this._RelativeOffsetOfLocalHeader & 0xFF000000) >> 24);
			}

			// actual filename
			Buffer.BlockCopy(fileNameBytes, 0, bytes, i, filenameLength);
			i += filenameLength;

			// "Extra field"
			if (this._Extra != null)
			{
				// workitem 11131
				//
				// copy from EntryHeader if available - it may have been updated.
				// if not, copy from Extra. This would be unnecessary if I just
				// updated the Extra field when updating EntryHeader, in
				// PostProcessOutput.

				//?? I don't understand why I wouldn't want to just use
				// the recalculated Extra field. ??

				// byte[] h = _EntryHeader ?? _Extra;
				// int offx = (h == _EntryHeader) ? 30 + filenameLength : 0;
				// Buffer.BlockCopy(h, offx, bytes, i, extraFieldLength);
				// i += extraFieldLength;

				var h = this._Extra;
				var offx = 0;
				Buffer.BlockCopy(h, offx, bytes, i, extraFieldLength);
				i += extraFieldLength;
			}

			// file (entry) comment
			if (commentLength != 0)
			{
				// now actually write the comment itself into the byte buffer
				Buffer.BlockCopy(_CommentBytes, 0, bytes, i, commentLength);
				// for (j = 0; (j < commentLength) && (i + j < bytes.Length); j++)
				//     bytes[i + j] = _CommentBytes[j];
				i += commentLength;
			}

			s.Write(bytes, 0, i);
		}


#if INFOZIP_UTF8
        static private bool FileNameIsUtf8(char[] FileNameChars)
        {
            bool isUTF8 = false;
            bool isUnicode = false;
            for (int j = 0; j < FileNameChars.Length; j++)
            {
                byte[] b = System.BitConverter.GetBytes(FileNameChars[j]);
                isUnicode |= (b.Length != 2);
                isUnicode |= (b[1] != 0);
                isUTF8 |= ((b[0] & 0x80) != 0);
            }

            return isUTF8;
        }
#endif


		private byte[] ConstructExtraField(bool forCentralDirectory)
		{
			var listOfBlocks = new System.Collections.Generic.List<byte[]>();
			byte[] block;

			// Conditionally emit an extra field with Zip64 information.  If the
			// Zip64 option is Always, we emit the field, before knowing that it's
			// necessary.  Later, if it turns out this entry does not need zip64,
			// we'll set the header ID to rubbish and the data will be ignored.
			// This results in additional overhead metadata in the zip file, but
			// it will be small in comparison to the entry data.
			//
			// On the other hand if the Zip64 option is AsNecessary and it's NOT
			// for the central directory, then we do the same thing.  Or, if the
			// Zip64 option is AsNecessary and it IS for the central directory,
			// and the entry requires zip64, then emit the header.
			if (this._container.Zip64 == Zip64Option.Always || (this._container.Zip64 == Zip64Option.AsNecessary && (!forCentralDirectory || _entryRequiresZip64.Value)))
			{
				// add extra field for zip64 here
				// workitem 7924
				var sz = 4 + (forCentralDirectory ? 28 : 16);
				block = new byte[sz];
				var i = 0;

				if (_presumeZip64 || forCentralDirectory)
				{
					// HeaderId = always use zip64 extensions.
					block[i++] = 0x01;
					block[i++] = 0x00;
				}
				else
				{
					// HeaderId = dummy data now, maybe set to 0x0001 (ZIP64) later.
					block[i++] = 0x99;
					block[i++] = 0x99;
				}

				// DataSize
				block[i++] = (byte)(sz - 4);  // decimal 28 or 16  (workitem 7924)
				block[i++] = 0x00;

				// The actual metadata - we may or may not have real values yet...

				// uncompressed size
				Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, block, i, 8);
				i += 8;
				// compressed size
				Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, block, i, 8);
				i += 8;

				// workitem 7924 - only include this if the "extra" field is for
				// use in the central directory.  It is unnecessary and not useful
				// for local header; makes WinZip choke.
				if (forCentralDirectory)
				{
					// relative offset
					Array.Copy(BitConverter.GetBytes(_RelativeOffsetOfLocalHeader), 0, block, i, 8);
					i += 8;

					// starting disk number
					Array.Copy(BitConverter.GetBytes(0), 0, block, i, 4);
				}
				listOfBlocks.Add(block);
			}


#if AESCRYPTO
			if (Encryption == EncryptionAlgorithm.WinZipAes128 || Encryption == EncryptionAlgorithm.WinZipAes256)
			{
				var i = 0;
				block = new byte[4 + 7];
				// extra field for WinZip AES
				// header id
				block[i++] = 0x01;
				block[i++] = 0x99;

				// data size
				block[i++] = 0x07;
				block[i++] = 0x00;

				// vendor number
				block[i++] = 0x01;  // AE-1 - means "Verify CRC"
				block[i++] = 0x00;

				// vendor id "AE"
				block[i++] = 0x41;
				block[i++] = 0x45;

				// key strength
				int keystrength = GetKeyStrengthInBits(Encryption);
				if (keystrength == 128)
					block[i] = 1;
				else if (keystrength == 256)
					block[i] = 3;
				else
					block[i] = 0xFF;
				i++;

				// actual compression method
				block[i++] = (byte)(_CompressionMethod & 0x00FF);
				block[i++] = (byte)(_CompressionMethod & 0xFF00);

				listOfBlocks.Add(block);
			}
#endif

			if (this._ntfsTimesAreSet && _emitNtfsTimes)
			{
				var i = 0;
				block = new byte[32 + 4];
				// HeaderId   2 bytes    0x000a == NTFS times
				// Datasize   2 bytes    32
				// reserved   4 bytes    ?? don't care
				// timetag    2 bytes    0x0001 == NTFS time
				// size       2 bytes    24 == 8 bytes each for ctime, mtime, atime
				// mtime      8 bytes    win32 ticks since win32epoch
				// atime      8 bytes    win32 ticks since win32epoch
				// ctime      8 bytes    win32 ticks since win32epoch
				// extra field for NTFS times
				// header id
				block[i++] = 0x0a;
				block[i++] = 0x00;

				// data size
				block[i++] = 32;
				block[i++] = 0;

				i += 4; // reserved

				// time tag
				block[i++] = 0x01;
				block[i++] = 0x00;

				// data size (again)
				block[i++] = 24;
				block[i++] = 0;

				var z = _Mtime.ToFileTime();
				Array.Copy(BitConverter.GetBytes(z), 0, block, i, 8);
				i += 8;
				z = _Atime.ToFileTime();
				Array.Copy(BitConverter.GetBytes(z), 0, block, i, 8);
				i += 8;
				z = _Ctime.ToFileTime();
				Array.Copy(BitConverter.GetBytes(z), 0, block, i, 8);
				i += 8;

				listOfBlocks.Add(block);
			}

			if (this._ntfsTimesAreSet && this._emitUnixTimes)
			{
				var i = 0;
				var len = 5 + 4;
				if (!forCentralDirectory)
					len += 8;
				block = new byte[len];
				// local form:
				// --------------
				// HeaderId   2 bytes    0x5455 == unix timestamp
				// Datasize   2 bytes    13
				// flags      1 byte     7 (low three bits all set)
				// mtime      4 bytes    seconds since unix epoch
				// atime      4 bytes    seconds since unix epoch
				// ctime      4 bytes    seconds since unix epoch
				//
				// central directory form:
				//---------------------------------
				// HeaderId   2 bytes    0x5455 == unix timestamp
				// Datasize   2 bytes    5
				// flags      1 byte     7 (low three bits all set)
				// mtime      4 bytes    seconds since unix epoch
				//
				// extra field for "unix" times
				// header id
				block[i++] = 0x55;
				block[i++] = 0x54;

				// data size
				block[i++] = unchecked((byte)(len - 4));
				block[i++] = 0;

				// flags
				block[i++] = 0x07;

				var z = unchecked((int)((_Mtime - _unixEpoch).TotalSeconds));
				Array.Copy(BitConverter.GetBytes(z), 0, block, i, 4);
				i += 4;
				if (!forCentralDirectory)
				{
					z = unchecked((int)((_Atime - _unixEpoch).TotalSeconds));
					Array.Copy(BitConverter.GetBytes(z), 0, block, i, 4);
					i += 4;
					z = unchecked((int)((_Ctime - _unixEpoch).TotalSeconds));
					Array.Copy(BitConverter.GetBytes(z), 0, block, i, 4);
					i += 4;
				}
				listOfBlocks.Add(block);
			}


			// inject other blocks here...


			// concatenate any blocks we've got:
			var aggregateBlock = Reflector<byte>.NullArray;
			if (listOfBlocks.Count > 0)
			{
				var totalLength = 0;
				var current = 0;
				for (var i = 0; i < listOfBlocks.Count; i++)
					totalLength += listOfBlocks[i].Length;
				aggregateBlock = new byte[totalLength];
				for (var i = 0; i < listOfBlocks.Count; i++)
				{
					System.Array.Copy(listOfBlocks[i], 0, aggregateBlock, current, listOfBlocks[i].Length);
					current += listOfBlocks[i].Length;
				}
			}

			return aggregateBlock;
		}



		// private System.Text.Encoding GenerateCommentBytes()
		// {
		//     var getEncoding = new Func<System.Text.Encoding>({
		//     switch (AlternateEncodingUsage)
		//     {
		//         case ZipOption.Always:
		//             return AlternateEncoding;
		//         case ZipOption.Never:
		//             return ibm437;
		//     }
		//     var cb = ibm437.GetBytes(_Comment);
		//     // need to use this form of GetString() for .NET CF
		//     string s1 = ibm437.GetString(cb, 0, cb.Length);
		//     if (s1 == _Comment)
		//         return ibm437;
		//     return AlternateEncoding;
		//     });
		//
		//     var encoding = getEncoding();
		//     _CommentBytes = encoding.GetBytes(_Comment);
		//     return encoding;
		// }


		private string NormalizeFileName()
		{
			// here, we need to flip the backslashes to forward-slashes,
			// also, we need to trim the \\server\share syntax from any UNC path.
			// and finally, we need to remove any leading .\

			var SlashFixed = FileName.Replace("\\", "/");
			var s1 = StringUtil.Null;
			if ((_TrimVolumeFromFullyQualifiedPaths) && (FileName.Length >= 3)
				&& (FileName[1] == ':') && (SlashFixed[2] == '/'))
			{
				// trim off volume letter, colon, and slash
				s1 = SlashFixed.Substring(3);
			}
			else if ((FileName.Length >= 4)
					 && ((SlashFixed[0] == '/') && (SlashFixed[1] == '/')))
			{
				var n = SlashFixed.IndexOf('/', 2);
				if (n == -1)
					throw new ArgumentException("The path for that entry appears to be badly formatted");

				s1 = SlashFixed.Substring(n + 1);
			}
			else if ((FileName.Length >= 3)
					 && ((SlashFixed[0] == '.') && (SlashFixed[1] == '/')))
			{
				// trim off dot and slash
				s1 = SlashFixed.Substring(2);
			}
			else
			{
				s1 = SlashFixed;
			}
			return s1;
		}


		/// <summary>
		///   generate and return a byte array that encodes the filename
		///   for the entry.
		/// </summary>
		/// <remarks>
		///   <para>
		///     side effects: generate and store into _CommentBytes the
		///     byte array for any comment attached to the entry. Also
		///     sets _actualEncoding to indicate the actual encoding
		///     used. The same encoding is used for both filename and
		///     comment.
		///   </para>
		/// </remarks>
		private byte[] GetEncodedFileNameBytes()
		{
			// workitem 6513
			var s1 = NormalizeFileName();

			switch (AlternateEncodingUsage)
			{
				case ZipOption.Always:
					if (!(this._Comment == null || this._Comment.Length == 0))
						this._CommentBytes = AlternateEncoding.GetBytes(_Comment);
					this._actualEncoding = AlternateEncoding;
					return AlternateEncoding.GetBytes(s1);

				case ZipOption.Never:
					if (!(this._Comment == null || this._Comment.Length == 0))
						this._CommentBytes = ibm437.GetBytes(_Comment);
					_actualEncoding = ibm437;
					return ibm437.GetBytes(s1);
			}

			// arriving here means AlternateEncodingUsage is "AsNecessary"

			// case ZipOption.AsNecessary:
			// workitem 6513: when writing, use the alternative encoding
			// only when _actualEncoding is not yet set (it can be set
			// during Read), and when ibm437 will not do.

			var result = ibm437.GetBytes(s1);
			// need to use this form of GetString() for .NET CF
			var s2 = ibm437.GetString(result, 0, result.Length);
			this._CommentBytes = null;
			if (s2 != s1)
			{
				// Encoding the filename with ibm437 does not allow round-trips.
				// Therefore, use the alternate encoding.  Assume it will work,
				// no checking of round trips here.
				result = AlternateEncoding.GetBytes(s1);
				if (this._Comment != null && _Comment.Length != 0)
					this._CommentBytes = AlternateEncoding.GetBytes(_Comment);
				this._actualEncoding = AlternateEncoding;
				return result;
			}

			this._actualEncoding = ibm437;

			// Using ibm437, FileName can be encoded without information
			// loss; now try the Comment.

			// if there is no comment, use ibm437.
			if (this._Comment == null || this._Comment.Length == 0)
				return result;

			// there is a comment. Get the encoded form.
			var cbytes = ibm437.GetBytes(_Comment);
			var c2 = ibm437.GetString(cbytes, 0, cbytes.Length);

			// Check for round-trip.
			if (c2 != Comment)
			{
				// Comment cannot correctly be encoded with ibm437.  Use
				// the alternate encoding.

				result = AlternateEncoding.GetBytes(s1);
				this._CommentBytes = AlternateEncoding.GetBytes(_Comment);
				this._actualEncoding = AlternateEncoding;
				return result;
			}

			// use IBM437
			this._CommentBytes = cbytes;
			return result;
		}



		private bool WantReadAgain()
		{
			if (this._UncompressedSize < 0x10)
				return false;

			if (this._CompressionMethod == 0x00)
				return false;

			if (CompressionLevel == PickGold.Zlib.CompressionLevel.None)
				return false;

			if (this._CompressedSize < _UncompressedSize)
				return false;

			if (this._Source == ZipEntrySource.Stream && !this._sourceStream.CanSeek)
				return false;

#if AESCRYPTO
			if (this._aesCrypto_forWrite != null && (CompressedSize - _aesCrypto_forWrite.SizeOfEncryptionMetadata) <= UncompressedSize + 0x10)
				return false;
#endif

			if (this._zipCrypto_forWrite != null && (CompressedSize - 12) <= UncompressedSize)
				return false;

			return true;
		}



		private void MaybeUnsetCompressionMethodForWriting(int cycle)
		{
			// if we've already tried with compression... turn it off this time
			if (cycle > 1)
			{
				this._CompressionMethod = 0x0;
				return;
			}
			// compression for directories = 0x00 (No Compression)
			if (IsDirectory)
			{
				this._CompressionMethod = 0x0;
				return;
			}

			if (this._Source == ZipEntrySource.ZipFile)
			{
				return; // do nothing
			}

			// If __FileDataPosition is zero, then that means we will get the data
			// from a file or stream.

			// It is never possible to compress a zero-length file, so we check for
			// this condition.

			if (this._Source == ZipEntrySource.Stream)
			{
				// workitem 7742
				if (_sourceStream != null && _sourceStream.CanSeek)
				{
					// Length prop will throw if CanSeek is false
					var fileLength = _sourceStream.Length;
					if (fileLength == 0)
					{
						_CompressionMethod = 0x00;
						return;
					}
				}
			}
			else if ((this._Source == ZipEntrySource.FileSystem) && (SharedUtilities.GetFileLength(LocalFileName) == 0L))
			{
				this._CompressionMethod = 0x00;
				return;
			}

			// Ok, we're getting the data to be compressed from a
			// non-zero-length file or stream, or a file or stream of
			// unknown length, and we presume that it is non-zero.  In
			// that case we check the callback to see if the app wants
			// to tell us whether to compress or not.
			if (SetCompression != null)
				CompressionLevel = SetCompression(LocalFileName, _FileNameInArchive);

			// finally, set CompressionMethod to None if CompressionLevel is None
			if (CompressionLevel == (short)PickGold.Zlib.CompressionLevel.None &&
				CompressionMethod == PickGold.Zip.CompressionMethod.Deflate)
				_CompressionMethod = 0x00;

			return;
		}



		// write the header info for an entry
		internal void WriteHeader(Stream s, int cycle)
		{
			// Must remember the offset, within the output stream, of this particular
			// entry header.
			//
			// This is for 2 reasons:
			//
			//  1. so we can determine the RelativeOffsetOfLocalHeader (ROLH) for
			//     use in the central directory.
			//  2. so we can seek backward in case there is an error opening or reading
			//     the file, and the application decides to skip the file. In this case,
			//     we need to seek backward in the output stream to allow the next entry
			//     to be added to the zipfile output stream.
			//
			// Normally you would just store the offset before writing to the output
			// stream and be done with it.  But the possibility to use split archives
			// makes this approach ineffective.  In split archives, each file or segment
			// is bound to a max size limit, and each local file header must not span a
			// segment boundary; it must be written contiguously.  If it will fit in the
			// current segment, then the ROLH is just the current Position in the output
			// stream.  If it won't fit, then we need a new file (segment) and the ROLH
			// is zero.
			//
			// But we only can know if it is possible to write a header contiguously
			// after we know the size of the local header, a size that varies with
			// things like filename length, comments, and extra fields.  We have to
			// compute the header fully before knowing whether it will fit.
			//
			// That takes care of item #1 above.  Now, regarding #2.  If an error occurs
			// while computing the local header, we want to just seek backward. The
			// exception handling logic (in the caller of WriteHeader) uses ROLH to
			// scroll back.
			//
			// All this means we have to preserve the starting offset before computing
			// the header, and also we have to compute the offset later, to handle the
			// case of split archives.

			var counter = s as CountingStream;

			// workitem 8098: ok (output)
			// This may change later, for split archives

			// Don't set _RelativeOffsetOfLocalHeader. Instead, set a temp variable.
			// This allows for re-streaming, where a zip entry might be read from a
			// zip archive (and maybe decrypted, and maybe decompressed) and then
			// written to another zip archive, with different settings for
			// compression method, compression level, or encryption algorithm.
			this._future_ROLH = (counter != null)
				? counter.ComputedPosition
				: s.Position;

			var j = 0;
			var i = 0;

			var block = new byte[30];

			// signature
			block[i++] = (byte)(ZipConstants.ZipEntrySignature & 0x000000FF);
			block[i++] = (byte)((ZipConstants.ZipEntrySignature & 0x0000FF00) >> 8);
			block[i++] = (byte)((ZipConstants.ZipEntrySignature & 0x00FF0000) >> 16);
			block[i++] = (byte)((ZipConstants.ZipEntrySignature & 0xFF000000) >> 24);

			// Design notes for ZIP64:
			//
			// The specification says that the header must include the Compressed
			// and Uncompressed sizes, as well as the CRC32 value.  When creating
			// a zip via streamed processing, these quantities are not known until
			// after the compression is done.  Thus, a typical way to do it is to
			// insert zeroes for these quantities, then do the compression, then
			// seek back to insert the appropriate values, then seek forward to
			// the end of the file data.
			//
			// There is also the option of using bit 3 in the GP bitfield - to
			// specify that there is a data descriptor after the file data
			// containing these three quantities.
			//
			// This works when the size of the quantities is known, either 32-bits
			// or 64 bits as with the ZIP64 extensions.
			//
			// With Zip64, the 4-byte fields are set to 0xffffffff, and there is a
			// corresponding data block in the "extra field" that contains the
			// actual Compressed, uncompressed sizes.  (As well as an additional
			// field, the "Relative Offset of Local Header")
			//
			// The problem is when the app desires to use ZIP64 extensions
			// optionally, only when necessary.  Suppose the library assumes no
			// zip64 extensions when writing the header, then after compression
			// finds that the size of the data requires zip64.  At this point, the
			// header, already written to the file, won't have the necessary data
			// block in the "extra field".  The size of the entry header is fixed,
			// so it is not possible to just "add on" the zip64 data block after
			// compressing the file.  On the other hand, always using zip64 will
			// break interoperability with many other systems and apps.
			//
			// The approach we take is to insert a 32-byte dummy data block in the
			// extra field, whenever zip64 is to be used "as necessary". This data
			// block will get the actual zip64 HeaderId and zip64 metadata if
			// necessary.  If not necessary, the data block will get a meaningless
			// HeaderId (0x1111), and will be filled with zeroes.
			//
			// When zip64 is actually in use, we also need to set the
			// VersionNeededToExtract field to 45.
			//
			// There is one additional wrinkle: using zip64 as necessary conflicts
			// with output to non-seekable devices.  The header is emitted and
			// must indicate whether zip64 is in use, before we know if zip64 is
			// necessary.  Because there is no seeking, the header can never be
			// changed.  Therefore, on non-seekable devices,
			// Zip64Option.AsNecessary is the same as Zip64Option.Always.
			//


			// version needed- see AppNote.txt.
			//
			// need v5.1 for PKZIP strong encryption, or v2.0 for no encryption or
			// for PK encryption, 4.5 for zip64.  We may reset this later, as
			// necessary or zip64.

			this._presumeZip64 = (this._container.Zip64 == Zip64Option.Always ||
							 (this._container.Zip64 == Zip64Option.AsNecessary && !s.CanSeek));
			var VersionNeededToExtract = (short)(_presumeZip64 ? 45 : 20);
#if BZIP
			if (this.CompressionMethod == PickGold.Zip.CompressionMethod.BZip2)
				VersionNeededToExtract = 46;
#endif

			// (i==4)
			block[i++] = (byte)(VersionNeededToExtract & 0x00FF);
			block[i++] = (byte)((VersionNeededToExtract & 0xFF00) >> 8);

			// Get byte array. Side effect: sets ActualEncoding.
			// Must determine encoding before setting the bitfield.
			// workitem 6513
			var fileNameBytes = GetEncodedFileNameBytes();
			var filenameLength = (short)fileNameBytes.Length;

			// general purpose bitfield
			// In the current implementation, this library uses only these bits
			// in the GP bitfield:
			//  bit 0 = if set, indicates the entry is encrypted
			//  bit 3 = if set, indicates the CRC, C and UC sizes follow the file data.
			//  bit 6 = strong encryption - for pkware's meaning of strong encryption
			//  bit 11 = UTF-8 encoding is used in the comment and filename


			// Here we set or unset the encryption bit.
			// _BitField may already be set, as with a ZipEntry added into ZipOutputStream, which
			// has bit 3 always set. We only want to set one bit
			if (this._Encryption == EncryptionAlgorithm.None)
				this._BitField &= ~1;  // encryption bit OFF
			else
				this._BitField |= 1;   // encryption bit ON


			// workitem 7941: WinZip does not the "strong encryption" bit  when using AES.
			// This "Strong Encryption" is a PKWare Strong encryption thing.
			//                 _BitField |= 0x0020;

			// set the UTF8 bit if necessary
#if SILVERLIGHT
            if (this._actualEncoding.WebName == "utf-8")
#else
			if (this._actualEncoding.CodePage == Encoding.UTF8.CodePage)
#endif
				_BitField |= 0x0800;

			// The PKZIP spec says that if bit 3 is set (0x0008) in the General
			// Purpose BitField, then the CRC, Compressed size, and uncompressed
			// size are written directly after the file data.
			//
			// These 3 quantities are normally present in the regular zip entry
			// header. But, they are not knowable until after the compression is
			// done. So, in the normal case, we
			//
			//  - write the header, using zeros for these quantities
			//  - compress the data, and incidentally compute these quantities.
			//  - seek back and write the correct values them into the header.
			//
			// This is nice because, while it is more complicated to write the zip
			// file, it is simpler and less error prone to read the zip file, and
			// as a result more applications can read zip files produced this way,
			// with those 3 quantities in the header.
			//
			// But if seeking in the output stream is not possible, then we need
			// to set the appropriate bitfield and emit these quantities after the
			// compressed file data in the output.
			//
			// workitem 7216 - having trouble formatting a zip64 file that is
			// readable by WinZip.  not sure why!  What I found is that setting
			// bit 3 and following all the implications, the zip64 file is
			// readable by WinZip 12. and Perl's IO::Compress::Zip .  Perl takes
			// an interesting approach - it always sets bit 3 if ZIP64 in use.
			// DotNetZip now does the same; this gives better compatibility with
			// WinZip 12.

			if (IsDirectory || cycle == 99)
			{
				// (cycle == 99) indicates a zero-length entry written by ZipOutputStream

				this._BitField &= ~0x0008;  // unset bit 3 - no "data descriptor" - ever
				this._BitField &= ~0x0001;  // unset bit 1 - no encryption - ever
				this.Encryption = EncryptionAlgorithm.None;
				this.Password = null;
			}
			else if (!s.CanSeek)
				this._BitField |= 0x0008;

#if DONT_GO_THERE
            else if (this.Encryption == EncryptionAlgorithm.PkzipWeak  &&
                     this._Source != ZipEntrySource.ZipFile)
            {
                // Set bit 3 to avoid the double-read perf issue.
                //
                // When PKZIP encryption is used, byte 11 of the encryption header is
                // used as a consistency check. It is normally set to the MSByte of the
                // CRC.  But this means the cRC must be known ebfore compression and
                // encryption, which means the entire stream has to be read twice.  To
                // avoid that, the high-byte of the time blob (when in DOS format) can
                // be used for the consistency check (byte 11 in the encryption header).
                // But this means the entry must have bit 3 set.
                //
                // Previously I used a more complex arrangement - using the methods like
                // FigureCrc32(), PrepOutputStream() and others, in order to manage the
                // seek-back in the source stream.  Why?  Because bit 3 is not always
                // friendly with third-party zip tools, like those on the Mac.
                //
                // This is why this code is still ifdef'd  out.
                //
                // Might consider making this yet another programmable option -
                // AlwaysUseBit3ForPkzip.  But that's for another day.
                //
                _BitField |= 0x0008;
            }
#endif

			// (i==6)
			block[i++] = (byte)(_BitField & 0x00FF);
			block[i++] = (byte)((_BitField & 0xFF00) >> 8);

			// Here, we want to set values for Compressed Size, Uncompressed Size,
			// and CRC.  If we have __FileDataPosition as not -1 (zero is a valid
			// FDP), then that means we are reading this zip entry from a zip
			// file, and we have good values for those quantities.
			//
			// If _FileDataPosition is -1, then we are constructing this Entry
			// from nothing.  We zero those quantities now, and we will compute
			// actual values for the three quantities later, when we do the
			// compression, and then seek back to write them into the appropriate
			// place in the header.
			if (this.__FileDataPosition == -1)
			{
				//_UncompressedSize = 0; // do not unset - may need this value for restream
				// _Crc32 = 0;           // ditto
				this._CompressedSize = 0;
				this._crcCalculated = false;
			}

			// set compression method here
			MaybeUnsetCompressionMethodForWriting(cycle);

			// (i==8) compression method
			block[i++] = (byte)(_CompressionMethod & 0x00FF);
			block[i++] = (byte)((_CompressionMethod & 0xFF00) >> 8);

			if (cycle == 99)
			{
				// (cycle == 99) indicates a zero-length entry written by ZipOutputStream
				SetZip64Flags();
			}

#if AESCRYPTO
			else if (Encryption == EncryptionAlgorithm.WinZipAes128 || Encryption == EncryptionAlgorithm.WinZipAes256)
			{
				i -= 2;
				block[i++] = 0x63;
				block[i++] = 0;
			}
#endif

			// LastMod
			_TimeBlob = PickGold.Zip.SharedUtilities.DateTimeToPacked(LastModified);

			// (i==10) time blob
			block[i++] = (byte)(_TimeBlob & 0x000000FF);
			block[i++] = (byte)((_TimeBlob & 0x0000FF00) >> 8);
			block[i++] = (byte)((_TimeBlob & 0x00FF0000) >> 16);
			block[i++] = (byte)((_TimeBlob & 0xFF000000) >> 24);

			// (i==14) CRC - if source==filesystem, this is zero now, actual value
			// will be calculated later.  if source==archive, this is a bonafide
			// value.
			block[i++] = (byte)(_Crc32 & 0x000000FF);
			block[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
			block[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
			block[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);

			if (_presumeZip64)
			{
				// (i==18) CompressedSize (Int32) and UncompressedSize - all 0xFF for now
				for (j = 0; j < 8; j++)
					block[i++] = 0xFF;
			}
			else
			{
				// (i==18) CompressedSize (Int32) - this value may or may not be
				// bonafide.  if source == filesystem, then it is zero, and we'll
				// learn it after we compress.  if source == archive, then it is
				// bonafide data.
				block[i++] = (byte)(_CompressedSize & 0x000000FF);
				block[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
				block[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
				block[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);

				// (i==22) UncompressedSize (Int32) - this value may or may not be
				// bonafide.
				block[i++] = (byte)(_UncompressedSize & 0x000000FF);
				block[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
				block[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
				block[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
			}

			// (i==26) filename length (Int16)
			block[i++] = (byte)(filenameLength & 0x00FF);
			block[i++] = (byte)((filenameLength & 0xFF00) >> 8);

			_Extra = ConstructExtraField(false);

			// (i==28) extra field length (short)
			Int16 extraFieldLength = (Int16)((_Extra == null) ? 0 : _Extra.Length);
			block[i++] = (byte)(extraFieldLength & 0x00FF);
			block[i++] = (byte)((extraFieldLength & 0xFF00) >> 8);

			// workitem 13542
			var bytes = new byte[i + filenameLength + extraFieldLength];

			// get the fixed portion
			Buffer.BlockCopy(block, 0, bytes, 0, i);
			//for (j = 0; j < i; j++) bytes[j] = block[j];

			// The filename written to the archive.
			Buffer.BlockCopy(fileNameBytes, 0, bytes, i, fileNameBytes.Length);
			// for (j = 0; j < fileNameBytes.Length; j++)
			//     bytes[i + j] = fileNameBytes[j];

			i += fileNameBytes.Length;

			// "Extra field"
			if (_Extra != null)
			{
				Buffer.BlockCopy(_Extra, 0, bytes, i, _Extra.Length);
				// for (j = 0; j < _Extra.Length; j++)
				//     bytes[i + j] = _Extra[j];
				i += _Extra.Length;
			}

			_LengthOfHeader = i;

			// handle split archives
			var zss = s as ZipSegmentedStream;
			if (zss != null)
			{
				zss.ContiguousWrite = true;
				UInt32 requiredSegment = zss.ComputeSegment(i);
				if (requiredSegment != zss.CurrentSegment)
					_future_ROLH = 0; // rollover!
				else
					_future_ROLH = zss.Position;

				_diskNumber = requiredSegment;
			}

			// validate the ZIP64 usage
			if (_container.Zip64 == Zip64Option.Never && (uint)_RelativeOffsetOfLocalHeader >= 0xFFFFFFFF)
				throw new ZipException("Offset within the zip archive exceeds 0xFFFFFFFF. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");


			// finally, write the header to the stream
			s.Write(bytes, 0, i);

			// now that the header is written, we can turn off the contiguous write restriction.
			if (zss != null)
				zss.ContiguousWrite = false;

			// Preserve this header data, we'll use it again later.
			// ..when seeking backward, to write again, after we have the Crc, compressed
			//   and uncompressed sizes.
			// ..and when writing the central directory structure.
			_EntryHeader = bytes;
		}




		private int FigureCrc32()
		{
			if (this._crcCalculated == false)
			{
				var input = Reflector<Stream>.DefaultValue;
				// get the original stream:
				if (this._Source == ZipEntrySource.WriteDelegate)
				{
					var output = new CalculatorStream(Stream.Null);
					// allow the application to write the data
					this._WriteDelegate(this.FileName, output);
					this._Crc32 = output.Crc;
				}
				else if (this._Source == ZipEntrySource.ZipFile)
				{
					// nothing to do - the CRC is already set
				}
				else
				{
					if (this._Source == ZipEntrySource.Stream)
					{
						PrepSourceStream();
						input = this._sourceStream;
					}
					else if (this._Source == ZipEntrySource.JitStream)
					{
						// allow the application to open the stream
						if (this._sourceStream == null)
							_sourceStream = this._OpenDelegate(this.FileName);
						PrepSourceStream();
						input = this._sourceStream;
					}
					else if (this._Source == ZipEntrySource.ZipOutputStream)
					{
					}
					else
					{
						//input = File.OpenRead(LocalFileName);
						input = File.Open(LocalFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
					}

					var crc32 = new CRC32();
					this._Crc32 = crc32.GetCrc32(input);

					if (this._sourceStream == null)
					{
#if NETCF
                        input.Close();
#else
						input.Dispose();
#endif
					}
				}
				this._crcCalculated = true;
			}
			return this._Crc32;
		}


		/// <summary>
		///   Stores the position of the entry source stream, or, if the position is
		///   already stored, seeks to that position.
		/// </summary>
		///
		/// <remarks>
		/// <para>
		///   This method is called in prep for reading the source stream.  If PKZIP
		///   encryption is used, then we need to calc the CRC32 before doing the
		///   encryption, because the CRC is used in the 12th byte of the PKZIP
		///   encryption header.  So, we need to be able to seek backward in the source
		///   when saving the ZipEntry. This method is called from the place that
		///   calculates the CRC, and also from the method that does the encryption of
		///   the file data.
		/// </para>
		///
		/// <para>
		///   The first time through, this method sets the _sourceStreamOriginalPosition
		///   field. Subsequent calls to this method seek to that position.
		/// </para>
		/// </remarks>
		private void PrepSourceStream()
		{
			if (this._sourceStream == null)
				throw new ZipException(String.Format("The input stream is null for entry '{0}'.", FileName));

			if (this._sourceStreamOriginalPosition != null)
			{
				// this will happen the 2nd cycle through, if the stream is seekable
				this._sourceStream.Position = this._sourceStreamOriginalPosition.Value;
			}
			else if (this._sourceStream.CanSeek)
			{
				// this will happen the first cycle through, if seekable
				this._sourceStreamOriginalPosition = new Nullable<Int64>(this._sourceStream.Position);
			}
			else if (this.Encryption == EncryptionAlgorithm.PkzipWeak)
			{
				// In general, using PKZIP encryption on a a zip entry whose input
				// comes from a non-seekable stream, is tricky.  Here's why:
				//
				// Byte 11 of the PKZIP encryption header is used for password
				// validation and consistency checknig.
				//
				// Normally, the highest byte of the CRC is used as the 11th (last) byte
				// in the PKZIP encryption header. This means the CRC must be known
				// before encryption is performed. Normally that means we read the full
				// data stream, compute the CRC, then seek back and read it again for
				// the compression+encryption phase. Obviously this is bad for
				// performance with a large input file.
				//
				// There's a twist in the ZIP spec (actually documented only in infozip
				// code, not in the spec itself) that allows the high-order byte of the
				// last modified time for the entry, when the lastmod time is in packed
				// (DOS) format, to be used for Byte 11 in the encryption header. In
				// this case, the bit 3 "data descriptor" must be used.
				//
				// An intelligent implementation would therefore force the use of the
				// bit 3 data descriptor when PKZIP encryption is in use, regardless.
				// This avoids the double-read of the stream to be encrypted.  So far,
				// DotNetZip doesn't do that; it just punts when the input stream is
				// non-seekable, and the output does not use Bit 3.
				//
				// The other option is to use the CRC when it is already available, eg,
				// when the source for the data is a ZipEntry (when the zip file is
				// being updated). In this case we already know the CRC and can just use
				// what we know.

				if (this._Source != ZipEntrySource.ZipFile && ((this._BitField & 0x0008) != 0x0008))
					throw new ZipException("It is not possible to use PKZIP encryption on a non-seekable input stream");
			}
		}


		/// <summary>
		/// Copy metadata that may have been changed by the app.  We do this when
		/// resetting the zipFile instance.  If the app calls Save() on a ZipFile, then
		/// tries to party on that file some more, we may need to Reset() it , which
		/// means re-reading the entries and then copying the metadata.  I think.
		/// </summary>
		internal void CopyMetaData(ZipEntry source)
		{
			this.__FileDataPosition = source.__FileDataPosition;
			this.CompressionMethod = source.CompressionMethod;
			this._CompressionMethod_FromZipFile = source._CompressionMethod_FromZipFile;
			this._CompressedFileDataSize = source._CompressedFileDataSize;
			this._UncompressedSize = source._UncompressedSize;
			this._BitField = source._BitField;
			this._Source = source._Source;
			this._LastModified = source._LastModified;
			this._Mtime = source._Mtime;
			this._Atime = source._Atime;
			this._Ctime = source._Ctime;
			this._ntfsTimesAreSet = source._ntfsTimesAreSet;
			this._emitUnixTimes = source._emitUnixTimes;
			this._emitNtfsTimes = source._emitNtfsTimes;
		}


		private void OnWriteBlock(Int64 bytesXferred, Int64 totalBytesToXfer)
		{
			if (this._container.ZipFile != null)
				this._ioOperationCanceled = _container.ZipFile.OnSaveBlock(this, bytesXferred, totalBytesToXfer);
		}



		private void _WriteEntryData(Stream s)
		{
			// Read in the data from the input stream (often a file in the filesystem),
			// and write it to the output stream, calculating a CRC on it as we go.
			// We will also compress and encrypt as necessary.

			var input = Reflector<Stream>.DefaultValue;
			var fdp = -1L;
			try
			{
				// Want to record the position in the zip file of the zip entry
				// data (as opposed to the metadata).  s.Position may fail on some
				// write-only streams, eg stdout or System.Web.HttpResponseStream.
				// We swallow that exception, because we don't care, in that case.
				// But, don't set __FileDataPosition directly.  It may be needed
				// to READ the zip entry from the zip file, if this is a
				// "re-stream" situation. In other words if the zip entry has
				// changed compression level, or compression method, or (maybe?)
				// encryption algorithm.  In that case if the original entry is
				// encrypted, we need __FileDataPosition to be the value for the
				// input zip file.  This s.Position is for the output zipfile.  So
				// we copy fdp to __FileDataPosition after this entry has been
				// (maybe) restreamed.
				fdp = s.Position;
			}
			catch (Exception) { }

			try
			{
				// Use fileLength for progress updates, and to decide whether we can
				// skip encryption and compression altogether (in case of length==zero)
				var fileLength = SetInputAndFigureFileLength(ref input);

				// Wrap a counting stream around the raw output stream:
				// This is the last thing that happens before the bits go to the
				// application-provided stream.
				//
				// Sometimes s is a CountingStream. Doesn't matter. Wrap it with a
				// counter anyway. We need to count at both levels.

				var entryCounter = new CountingStream(s);

				var encryptor = Reflector<Stream>.DefaultValue;
				var compressor = Reflector<Stream>.DefaultValue;

				if (fileLength != 0L)
				{
					// Maybe wrap an encrypting stream around the counter: This will
					// happen BEFORE output counting, and AFTER compression, if encryption
					// is used.
					encryptor = MaybeApplyEncryption(entryCounter);

					// Maybe wrap a compressing Stream around that.
					// This will happen BEFORE encryption (if any) as we write data out.
					compressor = MaybeApplyCompression(encryptor, fileLength);
				}
				else
				{
					encryptor = compressor = entryCounter;
				}

				// Wrap a CrcCalculatorStream around that.
				// This will happen BEFORE compression (if any) as we write data out.
				var output = new CalculatorStream(compressor, true);

				// output.Write() causes this flow:
				// calc-crc -> compress -> encrypt -> count -> actually write

				if (this._Source == ZipEntrySource.WriteDelegate)
				{
					// allow the application to write the data
					this._WriteDelegate(this.FileName, output);
				}
				else
				{
					// synchronously copy the input stream to the output stream-chain
					var buffer = new byte[BufferSize];
					var n = 0;
					while ((n = SharedUtilities.ReadWithRetry(input, buffer, 0, buffer.Length, FileName)) != 0)
					{
						output.Write(buffer, 0, n);
						OnWriteBlock(output.TotalBytesSlurped, fileLength);
						if (_ioOperationCanceled)
							break;
					}
				}

				FinishOutputStream(s, entryCounter, encryptor, compressor, output);
			}
			finally
			{
				if (this._Source == ZipEntrySource.JitStream)
				{
					// allow the application to close the stream
					if (this._CloseDelegate != null)
						this._CloseDelegate(this.FileName, input);
				}
				else if ((input as FileStream) != null)
				{
#if NETCF
                    input.Close();
#else
					input.Dispose();
#endif
				}
			}

			if (_ioOperationCanceled)
				return;

			// set FDP now, to allow for re-streaming
			this.__FileDataPosition = fdp;
			PostProcessOutput(s);
		}


		/// <summary>
		///   Set the input stream and get its length, if possible.  The length is
		///   used for progress updates, AND, to allow an optimization in case of
		///   a stream/file of zero length. In that case we skip the Encrypt and
		///   compression Stream. (like DeflateStream or BZip2OutputStream)
		/// </summary>
		private long SetInputAndFigureFileLength(ref Stream input)
		{
			var fileLength = -1L;
			// get the original stream:
			if (this._Source == ZipEntrySource.Stream)
			{
				PrepSourceStream();
				input = this._sourceStream;

				// Try to get the length, no big deal if not available.
				try
				{
					fileLength = this._sourceStream.Length;
				}
				catch (NotSupportedException) { }
			}
			else if (this._Source == ZipEntrySource.ZipFile)
			{
				// we are "re-streaming" the zip entry.
				string pwd = (_Encryption_FromZipFile == EncryptionAlgorithm.None) ? null : (this._Password ?? this._container.Password);
				this._sourceStream = InternalOpenReader(pwd);
				PrepSourceStream();
				input = this._sourceStream;
				fileLength = this._sourceStream.Length;
			}
			else if (this._Source == ZipEntrySource.JitStream)
			{
				// allow the application to open the stream
				if (this._sourceStream == null) _sourceStream = this._OpenDelegate(this.FileName);
				PrepSourceStream();
				input = this._sourceStream;
				try { fileLength = this._sourceStream.Length; }
				catch (NotSupportedException) { }
			}
			else if (this._Source == ZipEntrySource.FileSystem)
			{
				// workitem 7145
				FileShare fs = FileShare.ReadWrite;
#if !NETCF
				// FileShare.Delete is not defined for the Compact Framework
				fs |= FileShare.Delete;
#endif
				// workitem 8423
				input = File.Open(LocalFileName, FileMode.Open, FileAccess.Read, fs);
				fileLength = input.Length;
			}

			return fileLength;
		}



		internal void FinishOutputStream(Stream s, CountingStream entryCounter, Stream encryptor, Stream compressor, CalculatorStream output)
		{
			if (output == null) return;

			output.Close();

			// by calling Close() on the deflate stream, we write the footer bytes, as necessary.
			if ((compressor as PickGold.Zlib.DeflateStream) != null)
				compressor.Close();
#if BZIP
			else if ((compressor as PickGold.BZip2.BZip2OutputStream) != null)
				compressor.Close();
#if !NETCF
			else if ((compressor as PickGold.BZip2.ParallelBZip2OutputStream) != null)
				compressor.Close();
#endif
#endif

#if !NETCF
			else if ((compressor as PickGold.Zlib.ParallelDeflateOutputStream) != null)
				compressor.Close();
#endif

			encryptor.Flush();
			encryptor.Close();

			_LengthOfTrailer = 0;

			_UncompressedSize = output.TotalBytesSlurped;

#if AESCRYPTO
			WinZipAesCipherStream wzacs = encryptor as WinZipAesCipherStream;
			if (wzacs != null && _UncompressedSize > 0)
			{
				s.Write(wzacs.FinalAuthentication, 0, 10);
				_LengthOfTrailer += 10;
			}
#endif
			_CompressedFileDataSize = entryCounter.BytesWritten;
			_CompressedSize = _CompressedFileDataSize;   // may be adjusted
			_Crc32 = output.Crc;

			// Set _RelativeOffsetOfLocalHeader now, to allow for re-streaming
			StoreRelativeOffset();
		}




		internal void PostProcessOutput(Stream s)
		{
			var s1 = s as CountingStream;

			// workitem 8931 - for WriteDelegate.
			// The WriteDelegate changes things because there can be a zero-byte stream
			// written. In all other cases DotNetZip knows the length of the stream
			// before compressing and encrypting. In this case we have to circle back,
			// and omit all the crypto stuff - the GP bitfield, and the crypto header.
			if (_UncompressedSize == 0 && _CompressedSize == 0)
			{
				if (this._Source == ZipEntrySource.ZipOutputStream) return;  // nothing to do...

				if (_Password != null)
				{
					int headerBytesToRetract = 0;
					if (Encryption == EncryptionAlgorithm.PkzipWeak)
						headerBytesToRetract = 12;
#if AESCRYPTO
					else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
							 Encryption == EncryptionAlgorithm.WinZipAes256)
					{
						headerBytesToRetract = _aesCrypto_forWrite._Salt.Length + _aesCrypto_forWrite.GeneratedPV.Length;
					}
#endif
					if (this._Source == ZipEntrySource.ZipOutputStream && !s.CanSeek)
						throw new ZipException("Zero bytes written, encryption in use, and non-seekable output.");

					if (Encryption != EncryptionAlgorithm.None)
					{
						// seek back in the stream to un-output the security metadata
						s.Seek(-1 * headerBytesToRetract, SeekOrigin.Current);
						s.SetLength(s.Position);
						// workitem 10178
						PickGold.Zip.SharedUtilities.Workaround_Ladybug318918(s);

						// workitem 11131
						// adjust the count on the CountingStream as necessary
						if (s1 != null) s1.Adjust(headerBytesToRetract);

						// subtract the size of the security header from the _LengthOfHeader
						_LengthOfHeader -= headerBytesToRetract;
						__FileDataPosition -= headerBytesToRetract;
					}
					_Password = null;

					// turn off the encryption bit
					_BitField &= ~(0x0001);

					// copy the updated bitfield value into the header
					int j = 6;
					_EntryHeader[j++] = (byte)(_BitField & 0x00FF);
					_EntryHeader[j++] = (byte)((_BitField & 0xFF00) >> 8);

#if AESCRYPTO
					if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
						Encryption == EncryptionAlgorithm.WinZipAes256)
					{
						// Fix the extra field - overwrite the 0x9901 headerId
						// with dummy data. (arbitrarily, 0x9999)
						Int16 fnLength = (short)(_EntryHeader[26] + _EntryHeader[27] * 256);
						int offx = 30 + fnLength;
						int aesIndex = FindExtraFieldSegment(_EntryHeader, offx, 0x9901);
						if (aesIndex >= 0)
						{
							_EntryHeader[aesIndex++] = 0x99;
							_EntryHeader[aesIndex++] = 0x99;
						}
					}
#endif
				}

				CompressionMethod = 0;
				Encryption = EncryptionAlgorithm.None;
			}
			else if (_zipCrypto_forWrite != null
#if AESCRYPTO
 || _aesCrypto_forWrite != null
#endif
)
			{
				if (Encryption == EncryptionAlgorithm.PkzipWeak)
				{
					_CompressedSize += 12; // 12 extra bytes for the encryption header
				}
#if AESCRYPTO
				else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
						 Encryption == EncryptionAlgorithm.WinZipAes256)
				{
					// adjust the compressed size to include the variable (salt+pv)
					// security header and 10-byte trailer. According to the winzip AES
					// spec, that metadata is included in the "Compressed Size" figure
					// when encoding the zip archive.
					_CompressedSize += _aesCrypto_forWrite.SizeOfEncryptionMetadata;
				}
#endif
			}

			int i = 8;
			_EntryHeader[i++] = (byte)(_CompressionMethod & 0x00FF);
			_EntryHeader[i++] = (byte)((_CompressionMethod & 0xFF00) >> 8);

			i = 14;
			// CRC - the correct value now
			_EntryHeader[i++] = (byte)(_Crc32 & 0x000000FF);
			_EntryHeader[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
			_EntryHeader[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
			_EntryHeader[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);

			SetZip64Flags();

			// (i==26) filename length (Int16)
			Int16 filenameLength = (short)(_EntryHeader[26] + _EntryHeader[27] * 256);
			Int16 extraFieldLength = (short)(_EntryHeader[28] + _EntryHeader[29] * 256);

			if (_OutputUsesZip64.Value)
			{
				// VersionNeededToExtract - set to 45 to indicate zip64
				_EntryHeader[4] = (byte)(45 & 0x00FF);
				_EntryHeader[5] = 0x00;

				// workitem 7924 - don't need bit 3
				// // workitem 7917
				// // set bit 3 for ZIP64 compatibility with WinZip12
				// _BitField |= 0x0008;
				// _EntryHeader[6] = (byte)(_BitField & 0x00FF);

				// CompressedSize and UncompressedSize - 0xFF
				for (int j = 0; j < 8; j++)
					_EntryHeader[i++] = 0xff;

				// At this point we need to find the "Extra field" that follows the
				// filename.  We had already emitted it, but the data (uncomp, comp,
				// ROLH) was not available at the time we did so.  Here, we emit it
				// again, with final values.

				i = 30 + filenameLength;
				_EntryHeader[i++] = 0x01;  // zip64
				_EntryHeader[i++] = 0x00;

				i += 2; // skip over data size, which is 16+4

				Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, _EntryHeader, i, 8);
				i += 8;
				Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, _EntryHeader, i, 8);
			}
			else
			{
				// VersionNeededToExtract - reset to 20 since no zip64
				_EntryHeader[4] = (byte)(20 & 0x00FF);
				_EntryHeader[5] = 0x00;

				// CompressedSize - the correct value now
				i = 18;
				_EntryHeader[i++] = (byte)(_CompressedSize & 0x000000FF);
				_EntryHeader[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
				_EntryHeader[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
				_EntryHeader[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);

				// UncompressedSize - the correct value now
				_EntryHeader[i++] = (byte)(_UncompressedSize & 0x000000FF);
				_EntryHeader[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
				_EntryHeader[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
				_EntryHeader[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);

				// The HeaderId in the extra field header, is already dummied out.
				if (extraFieldLength != 0)
				{
					i = 30 + filenameLength;
					// For zip archives written by this library, if the zip64
					// header exists, it is the first header. Because of the logic
					// used when first writing the _EntryHeader bytes, the
					// HeaderId is not guaranteed to be any particular value.  So
					// we determine if the first header is a putative zip64 header
					// by examining the datasize.  UInt16 HeaderId =
					// (UInt16)(_EntryHeader[i] + _EntryHeader[i + 1] * 256);
					Int16 DataSize = (short)(_EntryHeader[i + 2] + _EntryHeader[i + 3] * 256);
					if (DataSize == 16)
					{
						// reset to Header Id to dummy value, effectively dummy-ing out the zip64 metadata
						_EntryHeader[i++] = 0x99;
						_EntryHeader[i++] = 0x99;
					}
				}
			}


#if AESCRYPTO

			if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
				Encryption == EncryptionAlgorithm.WinZipAes256)
			{
				// Must set compressionmethod to 0x0063 (decimal 99)
				//
				// and then set the compression method bytes inside the extra
				// field to the actual compression method value.

				i = 8;
				_EntryHeader[i++] = 0x63;
				_EntryHeader[i++] = 0;

				i = 30 + filenameLength;
				do
				{
					UInt16 HeaderId = (UInt16)(_EntryHeader[i] + _EntryHeader[i + 1] * 256);
					Int16 DataSize = (short)(_EntryHeader[i + 2] + _EntryHeader[i + 3] * 256);
					if (HeaderId != 0x9901)
					{
						// skip this header
						i += DataSize + 4;
					}
					else
					{
						i += 9;
						// actual compression method
						_EntryHeader[i++] = (byte)(_CompressionMethod & 0x00FF);
						_EntryHeader[i++] = (byte)(_CompressionMethod & 0xFF00);
					}
				} while (i < (extraFieldLength - 30 - filenameLength));
			}
#endif

			// finally, write the data.

			// workitem 7216 - sometimes we don't seek even if we CAN.  ASP.NET
			// Response.OutputStream, or stdout are non-seekable.  But we may also want
			// to NOT seek in other cases, eg zip64.  For all cases, we just check bit 3
			// to see if we want to seek.  There's one exception - if using a
			// ZipOutputStream, and PKZip encryption is in use, then we set bit 3 even
			// if the out is seekable. This is so the check on the last byte of the
			// PKZip Encryption Header can be done on the current time, as opposed to
			// the CRC, to prevent streaming the file twice.  So, test for
			// ZipOutputStream and seekable, and if so, seek back, even if bit 3 is set.

			if ((_BitField & 0x0008) != 0x0008 ||
				 (this._Source == ZipEntrySource.ZipOutputStream && s.CanSeek))
			{
				// seek back and rewrite the entry header
				var zss = s as ZipSegmentedStream;
				if (zss != null && _diskNumber != zss.CurrentSegment)
				{
					// In this case the entry header is in a different file,
					// which has already been closed. Need to re-open it.
					using (Stream hseg = ZipSegmentedStream.ForUpdate(this._container.ZipFile.Name, _diskNumber))
					{
						hseg.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);
						hseg.Write(_EntryHeader, 0, _EntryHeader.Length);
					}
				}
				else
				{
					// seek in the raw output stream, to the beginning of the header for
					// this entry.
					// workitem 8098: ok (output)
					s.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);

					// write the updated header to the output stream
					s.Write(_EntryHeader, 0, _EntryHeader.Length);

					// adjust the count on the CountingStream as necessary
					if (s1 != null) s1.Adjust(_EntryHeader.Length);

					// seek in the raw output stream, to the end of the file data
					// for this entry
					s.Seek(_CompressedSize, SeekOrigin.Current);
				}
			}

			// emit the descriptor - only if not a directory.
			if (((_BitField & 0x0008) == 0x0008) && !IsDirectory)
			{
				byte[] Descriptor = new byte[16 + (_OutputUsesZip64.Value ? 8 : 0)];
				i = 0;

				// signature
				Array.Copy(BitConverter.GetBytes(ZipConstants.ZipEntryDataDescriptorSignature), 0, Descriptor, i, 4);
				i += 4;

				// CRC - the correct value now
				Array.Copy(BitConverter.GetBytes(_Crc32), 0, Descriptor, i, 4);
				i += 4;

				// workitem 7917
				if (_OutputUsesZip64.Value)
				{
					// CompressedSize - the correct value now
					Array.Copy(BitConverter.GetBytes(_CompressedSize), 0, Descriptor, i, 8);
					i += 8;

					// UncompressedSize - the correct value now
					Array.Copy(BitConverter.GetBytes(_UncompressedSize), 0, Descriptor, i, 8);
					i += 8;
				}
				else
				{
					// CompressedSize - (lower 32 bits) the correct value now
					Descriptor[i++] = (byte)(_CompressedSize & 0x000000FF);
					Descriptor[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
					Descriptor[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
					Descriptor[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);

					// UncompressedSize - (lower 32 bits) the correct value now
					Descriptor[i++] = (byte)(_UncompressedSize & 0x000000FF);
					Descriptor[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
					Descriptor[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
					Descriptor[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);
				}

				// finally, write the trailing descriptor to the output stream
				s.Write(Descriptor, 0, Descriptor.Length);

				_LengthOfTrailer += Descriptor.Length;
			}
		}



		private void SetZip64Flags()
		{
			// zip64 housekeeping
			_entryRequiresZip64 = new Nullable<bool>
				(_CompressedSize >= 0xFFFFFFFF || _UncompressedSize >= 0xFFFFFFFF || _RelativeOffsetOfLocalHeader >= 0xFFFFFFFF);

			// validate the ZIP64 usage
			if (_container.Zip64 == Zip64Option.Never && _entryRequiresZip64.Value)
				throw new ZipException("Compressed or Uncompressed size, or offset exceeds the maximum value. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");

			_OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
		}



		/// <summary>
		///   Prepare the given stream for output - wrap it in a CountingStream, and
		///   then in a CRC stream, and an encryptor and deflator as appropriate.
		/// </summary>
		/// <remarks>
		///   <para>
		///     Previously this was used in ZipEntry.Write(), but in an effort to
		///     introduce some efficiencies in that method I've refactored to put the
		///     code inline.  This method still gets called by ZipOutputStream.
		///   </para>
		/// </remarks>
		internal void PrepOutputStream(Stream s, long streamLength, out CountingStream outputCounter, out Stream encryptor, out Stream compressor, out CalculatorStream output)
		{
			TraceWriteLine("PrepOutputStream: e({0}) comp({1}) crypto({2}) zf({3})",
						   FileName,
						   CompressionLevel,
						   Encryption,
						   _container.Name);

			// Wrap a counting stream around the raw output stream:
			// This is the last thing that happens before the bits go to the
			// application-provided stream.
			outputCounter = new CountingStream(s);

			// Sometimes the incoming "raw" output stream is already a CountingStream.
			// Doesn't matter. Wrap it with a counter anyway. We need to count at both
			// levels.

			if (streamLength != 0L)
			{
				// Maybe wrap an encrypting stream around that:
				// This will happen BEFORE output counting, and AFTER deflation, if encryption
				// is used.
				encryptor = MaybeApplyEncryption(outputCounter);

				// Maybe wrap a compressing Stream around that.
				// This will happen BEFORE encryption (if any) as we write data out.
				compressor = MaybeApplyCompression(encryptor, streamLength);
			}
			else
			{
				encryptor = compressor = outputCounter;
			}
			// Wrap a CrcCalculatorStream around that.
			// This will happen BEFORE compression (if any) as we write data out.
			output = new CalculatorStream(compressor, true);
		}



		private Stream MaybeApplyCompression(Stream s, long streamLength)
		{
			if (_CompressionMethod == 0x08 && CompressionLevel != PickGold.Zlib.CompressionLevel.None)
			{
#if !NETCF
				// ParallelDeflateThreshold == 0    means ALWAYS use parallel deflate
				// ParallelDeflateThreshold == -1L  means NEVER use parallel deflate
				// Other values specify the actual threshold.
				if (_container.ParallelDeflateThreshold == 0L ||
					(streamLength > _container.ParallelDeflateThreshold &&
					 _container.ParallelDeflateThreshold > 0L))
				{
					// This is sort of hacky.
					//
					// It's expensive to create a ParallelDeflateOutputStream, because
					// of the large memory buffers.  But the class is unlike most Stream
					// classes in that it can be re-used, so the caller can compress
					// multiple files with it, one file at a time.  The key is to call
					// Reset() on it, in between uses.
					//
					// The ParallelDeflateOutputStream is attached to the container
					// itself - there is just one for the entire ZipFile or
					// ZipOutputStream. So it gets created once, per save, and then
					// re-used many times.
					//
					// This approach will break when we go to a "parallel save"
					// approach, where multiple entries within the zip file are being
					// compressed and saved at the same time.  But for now it's ok.
					//

					// instantiate the ParallelDeflateOutputStream
					if (_container.ParallelDeflater == null)
					{
						_container.ParallelDeflater =
							new PickGold.Zlib.ParallelDeflateOutputStream(s,
																	   CompressionLevel,
																	   _container.Strategy,
																	   true);
						// can set the codec buffer size only before the first call to Write().
						if (_container.CodecBufferSize > 0)
							_container.ParallelDeflater.BufferSize = _container.CodecBufferSize;
						if (_container.ParallelDeflateMaxBufferPairs > 0)
							_container.ParallelDeflater.MaxBufferPairs =
								_container.ParallelDeflateMaxBufferPairs;
					}
					// reset it with the new stream
					PickGold.Zlib.ParallelDeflateOutputStream o1 = _container.ParallelDeflater;
					o1.Reset(s);
					return o1;
				}
#endif
				var o = new PickGold.Zlib.DeflateStream(s, PickGold.Zlib.CompressionMode.Compress,
													 CompressionLevel,
													 true);
				if (_container.CodecBufferSize > 0)
					o.BufferSize = _container.CodecBufferSize;
				o.Strategy = _container.Strategy;
				return o;
			}


#if BZIP
			if (_CompressionMethod == 0x0c)
			{
#if !NETCF
				if (_container.ParallelDeflateThreshold == 0L ||
					(streamLength > _container.ParallelDeflateThreshold &&
					 _container.ParallelDeflateThreshold > 0L))
				{

					var o1 = new PickGold.BZip2.ParallelBZip2OutputStream(s, true);
					return o1;
				}
#endif
				var o = new PickGold.BZip2.BZip2OutputStream(s, true);
				return o;
			}
#endif

			return s;
		}



		private Stream MaybeApplyEncryption(Stream s)
		{
			if (Encryption == EncryptionAlgorithm.PkzipWeak)
			{
				TraceWriteLine("MaybeApplyEncryption: e({0}) PKZIP", FileName);

				return new ZipCipherStream(s, _zipCrypto_forWrite, CryptoMode.Encrypt);
			}
#if AESCRYPTO
			if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
					 Encryption == EncryptionAlgorithm.WinZipAes256)
			{
				TraceWriteLine("MaybeApplyEncryption: e({0}) AES", FileName);

				return new WinZipAesCipherStream(s, _aesCrypto_forWrite, CryptoMode.Encrypt);
			}
#endif
			TraceWriteLine("MaybeApplyEncryption: e({0}) None", FileName);

			return s;
		}



		private void OnZipErrorWhileSaving(Exception e)
		{
			if (_container.ZipFile != null)
				_ioOperationCanceled = _container.ZipFile.OnZipErrorSaving(this, e);
		}



		internal void Write(Stream s)
		{
			var cs1 = s as CountingStream;
			var zss1 = s as ZipSegmentedStream;

			bool done = false;
			do
			{
				try
				{
					// When the app is updating a zip file, it may be possible to
					// just copy data for a ZipEntry from the source zipfile to the
					// destination, as a block, without decompressing and
					// recompressing, etc.  But, in some cases the app modifies the
					// properties on a ZipEntry prior to calling Save(). A change to
					// any of the metadata - the FileName, CompressioLeve and so on,
					// means DotNetZip cannot simply copy through the existing
					// ZipEntry data unchanged.
					//
					// There are two cases:
					//
					//  1. Changes to only metadata, which means the header and
					//     central directory must be changed.
					//
					//  2. Changes to the properties that affect the compressed
					//     stream, such as CompressionMethod, CompressionLevel, or
					//     EncryptionAlgorithm. In this case, DotNetZip must
					//     "re-stream" the data: the old entry data must be maybe
					//     decrypted, maybe decompressed, then maybe re-compressed
					//     and maybe re-encrypted.
					//
					// This test checks if the source for the entry data is a zip file, and
					// if a restream is necessary.  If NOT, then it just copies through
					// one entry, potentially changing the metadata.

					if (_Source == ZipEntrySource.ZipFile && !_restreamRequiredOnSave)
					{
						CopyThroughOneEntry(s);
						return;
					}

					// Is the entry a directory?  If so, the write is relatively simple.
					if (IsDirectory)
					{
						WriteHeader(s, 1);
						StoreRelativeOffset();
						_entryRequiresZip64 = new Nullable<bool>(_RelativeOffsetOfLocalHeader >= 0xFFFFFFFF);
						_OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
						// handle case for split archives
						if (zss1 != null)
							_diskNumber = zss1.CurrentSegment;

						return;
					}

					// At this point, the source for this entry is not a directory, and
					// not a previously created zip file, or the source for the entry IS
					// a previously created zip but the settings whave changed in
					// important ways and therefore we will need to process the
					// bytestream (compute crc, maybe compress, maybe encrypt) in order
					// to write the content into the new zip.
					//
					// We do this in potentially 2 passes: The first time we do it as
					// requested, maybe with compression and maybe encryption.  If that
					// causes the bytestream to inflate in size, and if compression was
					// on, then we turn off compression and do it again.


					bool readAgain = true;
					int nCycles = 0;
					do
					{
						nCycles++;

						WriteHeader(s, nCycles);

						// write the encrypted header
						WriteSecurityMetadata(s);

						// write the (potentially compressed, potentially encrypted) file data
						_WriteEntryData(s);

						// track total entry size (including the trailing descriptor and MAC)
						_TotalEntrySize = _LengthOfHeader + _CompressedFileDataSize + _LengthOfTrailer;

						// The file data has now been written to the stream, and
						// the file pointer is positioned directly after file data.

						if (nCycles > 1) readAgain = false;
						else if (!s.CanSeek) readAgain = false;
						else readAgain = WantReadAgain();

						if (readAgain)
						{
							// Seek back in the raw output stream, to the beginning of the file
							// data for this entry.

							// handle case for split archives
							if (zss1 != null)
							{
								// Console.WriteLine("***_diskNumber/first: {0}", _diskNumber);
								// Console.WriteLine("***_diskNumber/current: {0}", zss.CurrentSegment);
								zss1.TruncateBackward(_diskNumber, _RelativeOffsetOfLocalHeader);
							}
							else
								// workitem 8098: ok (output).
								s.Seek(_RelativeOffsetOfLocalHeader, SeekOrigin.Begin);

							// If the last entry expands, we read again; but here, we must
							// truncate the stream to prevent garbage data after the
							// end-of-central-directory.

							// workitem 8098: ok (output).
							s.SetLength(s.Position);

							// Adjust the count on the CountingStream as necessary.
							if (cs1 != null) cs1.Adjust(_TotalEntrySize);
						}
					}
					while (readAgain);
					_skippedDuringSave = false;
					done = true;
				}
				catch (System.Exception exc1)
				{
					ZipErrorAction orig = this.ZipErrorAction;
					int loop = 0;
					do
					{
						if (ZipErrorAction == ZipErrorAction.Throw)
							throw;

						if (ZipErrorAction == ZipErrorAction.Skip ||
							ZipErrorAction == ZipErrorAction.Retry)
						{
							// must reset file pointer here.
							// workitem 13903 - seek back only when necessary
							long p1 = (cs1 != null)
								? cs1.ComputedPosition
								: s.Position;
							long delta = p1 - _future_ROLH;
							if (delta > 0)
							{
								s.Seek(delta, SeekOrigin.Current); // may throw
								long p2 = s.Position;
								s.SetLength(s.Position);  // to prevent garbage if this is the last entry
								if (cs1 != null) cs1.Adjust(p1 - p2);
							}
							if (ZipErrorAction == ZipErrorAction.Skip)
							{
								WriteStatus("Skipping file {0} (exception: {1})", LocalFileName, exc1.ToString());

								_skippedDuringSave = true;
								done = true;
							}
							else
								this.ZipErrorAction = orig;
							break;
						}

						if (loop > 0) throw;

						if (ZipErrorAction == ZipErrorAction.InvokeErrorEvent)
						{
							OnZipErrorWhileSaving(exc1);
							if (_ioOperationCanceled)
							{
								done = true;
								break;
							}
						}
						loop++;
					}
					while (true);
				}
			}
			while (!done);
		}


		internal void StoreRelativeOffset()
		{
			_RelativeOffsetOfLocalHeader = _future_ROLH;
		}



		internal void NotifySaveComplete()
		{
			// When updating a zip file, there are two contexts for properties
			// like Encryption or CompressionMethod - the values read from the
			// original zip file, and the values used in the updated zip file.
			// The _FromZipFile versions are the originals.  At the end of a save,
			// these values are the same.  So we need to update them.  This takes
			// care of the boundary case where a single zipfile instance can be
			// saved multiple times, with distinct changes to the properties on
			// the entries, in between each Save().
			_Encryption_FromZipFile = _Encryption;
			_CompressionMethod_FromZipFile = _CompressionMethod;
			_restreamRequiredOnSave = false;
			_metadataChanged = false;
			//_Source = ZipEntrySource.None;
			_Source = ZipEntrySource.ZipFile; // workitem 10694
		}


		internal void WriteSecurityMetadata(Stream outstream)
		{
			if (Encryption == EncryptionAlgorithm.None)
				return;

			string pwd = this._Password;

			// special handling for source == ZipFile.
			// Want to support the case where we re-stream an encrypted entry. This will involve,
			// at runtime, reading, decrypting, and decompressing from the original zip file, then
			// compressing, encrypting, and writing to the output zip file.

			// If that's what we're doing, and the password hasn't been set on the entry,
			// we use the container (ZipFile/ZipOutputStream) password to decrypt.
			// This test here says to use the container password to re-encrypt, as well,
			// with that password, if the entry password is null.

			if (this._Source == ZipEntrySource.ZipFile && pwd == null)
				pwd = this._container.Password;

			if (pwd == null)
			{
				_zipCrypto_forWrite = null;
#if AESCRYPTO
				_aesCrypto_forWrite = null;
#endif
				return;
			}

			TraceWriteLine("WriteSecurityMetadata: e({0}) crypto({1}) pw({2})",
						   FileName, Encryption.ToString(), pwd);

			if (Encryption == EncryptionAlgorithm.PkzipWeak)
			{
				// If PKZip (weak) encryption is in use, then the encrypted entry data
				// is preceded by 12-byte "encryption header" for the entry.

				_zipCrypto_forWrite = ZipCrypto.ForWrite(pwd);

				// generate the random 12-byte header:
				var rnd = new System.Random();
				byte[] encryptionHeader = new byte[12];
				rnd.NextBytes(encryptionHeader);

				// workitem 8271
				if ((this._BitField & 0x0008) == 0x0008)
				{
					// In the case that bit 3 of the general purpose bit flag is set to
					// indicate the presence of a 'data descriptor' (signature
					// 0x08074b50), the last byte of the decrypted header is sometimes
					// compared with the high-order byte of the lastmodified time,
					// rather than the high-order byte of the CRC, to verify the
					// password.
					//
					// This is not documented in the PKWare Appnote.txt.
					// This was discovered this by analysis of the Crypt.c source file in the
					// InfoZip library
					// http://www.info-zip.org/pub/infozip/

					// Also, winzip insists on this!
					_TimeBlob = PickGold.Zip.SharedUtilities.DateTimeToPacked(LastModified);
					encryptionHeader[11] = (byte)((this._TimeBlob >> 8) & 0xff);
				}
				else
				{
					// When bit 3 is not set, the CRC value is required before
					// encryption of the file data begins. In this case there is no way
					// around it: must read the stream in its entirety to compute the
					// actual CRC before proceeding.
					FigureCrc32();
					encryptionHeader[11] = (byte)((this._Crc32 >> 24) & 0xff);
				}

				// Encrypt the random header, INCLUDING the final byte which is either
				// the high-order byte of the CRC32, or the high-order byte of the
				// _TimeBlob.  Must do this BEFORE encrypting the file data.  This
				// step changes the state of the cipher, or in the words of the PKZIP
				// spec, it "further initializes" the cipher keys.

				byte[] cipherText = _zipCrypto_forWrite.EncryptMessage(encryptionHeader, encryptionHeader.Length);

				// Write the ciphered bonafide encryption header.
				outstream.Write(cipherText, 0, cipherText.Length);
				_LengthOfHeader += cipherText.Length;  // 12 bytes
			}

#if AESCRYPTO
			else if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
				Encryption == EncryptionAlgorithm.WinZipAes256)
			{
				// If WinZip AES encryption is in use, then the encrypted entry data is
				// preceded by a variable-sized Salt and a 2-byte "password
				// verification" value for the entry.

				int keystrength = GetKeyStrengthInBits(Encryption);
				_aesCrypto_forWrite = WinZipAesCrypto.Generate(pwd, keystrength);
				outstream.Write(_aesCrypto_forWrite.Salt, 0, _aesCrypto_forWrite._Salt.Length);
				outstream.Write(_aesCrypto_forWrite.GeneratedPV, 0, _aesCrypto_forWrite.GeneratedPV.Length);
				_LengthOfHeader += _aesCrypto_forWrite._Salt.Length + _aesCrypto_forWrite.GeneratedPV.Length;

				TraceWriteLine("WriteSecurityMetadata: AES e({0}) keybits({1}) _LOH({2})",
							   FileName, keystrength, _LengthOfHeader);

			}
#endif

		}



		private void CopyThroughOneEntry(Stream outStream)
		{
			// Just read the entry from the existing input zipfile and write to the output.
			// But, if metadata has changed (like file times or attributes), or if the ZIP64
			// option has changed, we can re-stream the entry data but must recompute the
			// metadata.
			if (this.LengthOfHeader == 0)
				throw new BadStateException("Bad header length.");

			// is it necessary to re-constitute new metadata for this entry?
			bool needRecompute = _metadataChanged ||
				(this.ArchiveStream is ZipSegmentedStream) ||
				(outStream is ZipSegmentedStream) ||
				(_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Never) ||
				(!_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Always);

			if (needRecompute)
				CopyThroughWithRecompute(outStream);
			else
				CopyThroughWithNoChange(outStream);

			// zip64 housekeeping
			_entryRequiresZip64 = new Nullable<bool>
				(_CompressedSize >= 0xFFFFFFFF || _UncompressedSize >= 0xFFFFFFFF ||
				_RelativeOffsetOfLocalHeader >= 0xFFFFFFFF
				);

			_OutputUsesZip64 = new Nullable<bool>(_container.Zip64 == Zip64Option.Always || _entryRequiresZip64.Value);
		}



		private void CopyThroughWithRecompute(Stream outstream)
		{
			int n;
			byte[] bytes = new byte[BufferSize];
			var input = new CountingStream(this.ArchiveStream);

			long origRelativeOffsetOfHeader = _RelativeOffsetOfLocalHeader;

			// The header length may change due to rename of file, add a comment, etc.
			// We need to retain the original.
			int origLengthOfHeader = LengthOfHeader; // including crypto bytes!

			// WriteHeader() has the side effect of changing _RelativeOffsetOfLocalHeader
			// and setting _LengthOfHeader.  While ReadHeader() reads the crypto header if
			// present, WriteHeader() does not write the crypto header.
			WriteHeader(outstream, 0);
			StoreRelativeOffset();

			if (!this.FileName.EndsWith("/"))
			{
				// Not a directory; there is file data.
				// Seek to the beginning of the entry data in the input stream.

				long pos = origRelativeOffsetOfHeader + origLengthOfHeader;
				int len = GetLengthOfCryptoHeaderBytes(_Encryption_FromZipFile);
				pos -= len; // want to keep the crypto header
				_LengthOfHeader += len;

				input.Seek(pos, SeekOrigin.Begin);

				// copy through everything after the header to the output stream
				long remaining = this._CompressedSize;

				while (remaining > 0)
				{
					len = (remaining > bytes.Length) ? bytes.Length : (int)remaining;

					// read
					n = input.Read(bytes, 0, len);
					//_CheckRead(n);

					// write
					outstream.Write(bytes, 0, n);
					remaining -= n;
					OnWriteBlock(input.BytesRead, this._CompressedSize);
					if (_ioOperationCanceled)
						break;
				}

				// bit 3 descriptor
				if ((this._BitField & 0x0008) == 0x0008)
				{
					int size = 16;
					if (_InputUsesZip64) size += 8;
					byte[] Descriptor = new byte[size];
					input.Read(Descriptor, 0, size);

					if (_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Never)
					{
						// original descriptor was 24 bytes, now we need 16.
						// Must check for underflow here.
						// signature + CRC.
						outstream.Write(Descriptor, 0, 8);

						// Compressed
						if (_CompressedSize > 0xFFFFFFFF)
							throw new InvalidOperationException("ZIP64 is required");
						outstream.Write(Descriptor, 8, 4);

						// UnCompressed
						if (_UncompressedSize > 0xFFFFFFFF)
							throw new InvalidOperationException("ZIP64 is required");
						outstream.Write(Descriptor, 16, 4);
						_LengthOfTrailer -= 8;
					}
					else if (!_InputUsesZip64 && _container.UseZip64WhenSaving == Zip64Option.Always)
					{
						// original descriptor was 16 bytes, now we need 24
						// signature + CRC
						byte[] pad = new byte[4];
						outstream.Write(Descriptor, 0, 8);
						// Compressed
						outstream.Write(Descriptor, 8, 4);
						outstream.Write(pad, 0, 4);
						// UnCompressed
						outstream.Write(Descriptor, 12, 4);
						outstream.Write(pad, 0, 4);
						_LengthOfTrailer += 8;
					}
					else
					{
						// same descriptor on input and output. Copy it through.
						outstream.Write(Descriptor, 0, size);
						//_LengthOfTrailer += size;
					}
				}
			}

			_TotalEntrySize = _LengthOfHeader + _CompressedFileDataSize + _LengthOfTrailer;
		}


		private void CopyThroughWithNoChange(Stream outstream)
		{
			int n;
			byte[] bytes = new byte[BufferSize];
			var input = new CountingStream(this.ArchiveStream);

			// seek to the beginning of the entry data in the input stream
			input.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);

			if (this._TotalEntrySize == 0)
			{
				// We've never set the length of the entry.
				// Set it here.
				this._TotalEntrySize = this._LengthOfHeader + this._CompressedFileDataSize + _LengthOfTrailer;

				// The CompressedSize includes all the leading metadata associated
				// to encryption, if any, as well as the compressed data, or
				// compressed-then-encrypted data, and the trailer in case of AES.

				// The CompressedFileData size is the same, less the encryption
				// framing data (12 bytes header for PKZip; 10/18 bytes header and
				// 10 byte trailer for AES).

				// The _LengthOfHeader includes all the zip entry header plus the
				// crypto header, if any.  The _LengthOfTrailer includes the
				// 10-byte MAC for AES, where appropriate, and the bit-3
				// Descriptor, where applicable.
			}


			// workitem 5616
			// remember the offset, within the output stream, of this particular entry header.
			// This may have changed if any of the other entries changed (eg, if a different
			// entry was removed or added.)
			var counter = outstream as CountingStream;
			_RelativeOffsetOfLocalHeader = (counter != null)
				? counter.ComputedPosition
				: outstream.Position;  // BytesWritten

			// copy through the header, filedata, trailer, everything...
			long remaining = this._TotalEntrySize;
			while (remaining > 0)
			{
				int len = (remaining > bytes.Length) ? bytes.Length : (int)remaining;

				// read
				n = input.Read(bytes, 0, len);
				//_CheckRead(n);

				// write
				outstream.Write(bytes, 0, n);
				remaining -= n;
				OnWriteBlock(input.BytesRead, this._TotalEntrySize);
				if (_ioOperationCanceled)
					break;
			}
		}




		[System.Diagnostics.ConditionalAttribute("Trace")]
		private void TraceWriteLine(string format, params object[] varParams)
		{
			if (!Monitor.TryEnter(_outputLock, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(_outputLock);
			try
			{
				int tid = System.Threading.Thread.CurrentThread.GetHashCode();
#if ! (NETCF || SILVERLIGHT)
				Console.ForegroundColor = (ConsoleColor)(tid % 8 + 8);
#endif
				Console.Write("{0:000} ZipEntry.Write ", tid);
				Console.WriteLine(format, varParams);
#if ! (NETCF || SILVERLIGHT)
				Console.ResetColor();
#endif
			}
			finally
			{
				Monitor.Exit(_outputLock);
			}
		}

		private object _outputLock = new Object();
	}
}
